package org.aspectsense.middleware;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;

import org.aspectsense.middleware.annotations.RequiredService;
import org.aspectsense.middleware.annotations.Service;
import org.aspectsense.middleware.interfaces.IComponentContainer;


/**
 * This class is a wrapper for each Component(i.e. Service Provider/Implementer)
 * @author Costas Kakousis
 *
 */
public class ComponentContainer implements IComponentContainer {
	
	private HashMap<String, Annotation[]> metadata;
	private Class<? extends Object> componentObject;
	private String[] requiredServiceNames;
	private String[] providedServiceNames;
	private boolean isResolved;
	private String bundleName;
	private String providerName;
	private String containerName;
	private HashMap<String, String> service2Utility;
	private boolean isEndUser;
	

	
	public ComponentContainer (Class<? extends Object> serviceProviderObj, String bundleName){
		assert (serviceProviderObj!=null);
		this.componentObject = serviceProviderObj;
		containerName = serviceProviderObj.getSimpleName()+"_Container";
		providerName = serviceProviderObj.getCanonicalName();
		this.bundleName = bundleName;
		service2Utility = new HashMap<String, String>();
		init();
	}

	private void init(){
		this.setMetadata();
		setRequiredServiceNames();
		setProvidedServices();
		
	}


	private void setProvidedServices() {
		Service[] serviceAnnotations = (Service[]) metadata.get("Service");
		ArrayList <String> provServiceNames =  new ArrayList<String>();
		for (Service s:serviceAnnotations)
		{
			provServiceNames.add(s.serviceType().getCanonicalName());
			setProvidedServiceUtilityFunction(s.serviceType().getCanonicalName(), s.utilityFunction());			
		}
		this.providedServiceNames = provServiceNames.toArray(new String[provServiceNames.size()]);
	}
	
	
	private void setProvidedServiceUtilityFunction (String serviceName, String utilityFunction){
		this.service2Utility.put(serviceName, utilityFunction);
	}
	
	public String getProvidedServiceUtilityFunction (String serviceName){
		if (service2Utility.get(serviceName)!=null)
			return this.service2Utility.get(serviceName);
		else
			return null;
	}
	/**
	 * From the service provider class, get all annotated fields.
	 * From these fields take those that are annotated as required services
	 * and from them take the class canonical name. This is the name of
	 * the required service that the component of this container needs in
	 * order to provide its services.
	 */
	private void setRequiredServiceNames (){
		
    	final Field[] fields;
    	final Class<?> clazz = this.componentObject;
    	ArrayList <String>requiredServiceNames = new ArrayList <String>();
        fields = clazz.getDeclaredFields();
            
        for (Field field:fields)
        {
        	
        	if (field.getDeclaredAnnotations().length>0)
        	{
        		for (Annotation an:field.getDeclaredAnnotations())
        		{
        			
        			if(an.annotationType().getSimpleName().equals("RequiredService"))
        			{
        				requiredServiceNames.add(field.getType().getCanonicalName());
        			
        			}
        		}
        	}
        }
       
       this.requiredServiceNames=requiredServiceNames.toArray(new String[requiredServiceNames.size()]);
       if( this.requiredServiceNames.length==0)
    	  resolve(true);
    	   
       else
    	   resolve(false);
       
	}
	
	public String getServiceUtilityFunction (String serviceName){
		
		if (service2Utility!=null)
			return (String) service2Utility.get(serviceName);
			
		return null;
	
	}
	
	public void resolve(boolean value){
		this.isResolved=value;
	}
	
	public boolean isResolved(){
		return this.isResolved;
	}
	
	public String getInfo (){
		
		StringBuffer info = new StringBuffer();
		info.append(" <strong>Component Name:</strong>"+componentObject.getSimpleName()+" <br/> <strong>Bundle Name:</strong>"+bundleName+"<br/>");
		info.append(" <strong>Provides:</strong> <br/>");
		
		for (String s:this.providedServiceNames)
		{
			info.append("  "+s+" (Utility Function: "+service2Utility.get(s)+")<br/>");
		}
		info.append(" <strong>Requires:</strong><br/>");
		
		for (String s:requiredServiceNames){
			
			info.append("  "+s+"<br/>");
		}
		
		return info.toString();
	}

	/**
	 * Get all the annotations from the service provider class
	 * and create a metadata <code>HashMap</code>
	 * @param serviceProviderClass
	 */
	public void setMetadata() {

		Class<? extends Object> serviceProviderClass = this.componentObject;
    	RequiredService[] requiredServiceAnnotations = null;
        final Service[] serviceAnnotation = getServiceAnnotations(serviceProviderClass);
       
        requiredServiceAnnotations = getRequiredServiceAnnotation(serviceProviderClass);
       
        if(serviceAnnotation!=null || requiredServiceAnnotations!=null )
        {	
        	this.metadata = new HashMap<String, Annotation[]>();
        	if (serviceAnnotation!=null)
        		metadata.put("Service",serviceAnnotation);
	        if (requiredServiceAnnotations!=null)
	        	metadata.put("RequiredService",requiredServiceAnnotations);
        }
		
	}

	//------------------Public Getters---------------
	public HashMap<String, Annotation[]> getMetadata() {
		return metadata;
	}
	
	public String[] getProvidedServicesClassNames (){
		return this.providedServiceNames;
	}
	
	public String[] getRequiredServicesClassNames (){
		return this.requiredServiceNames;
	}
	
	public String getContainerName(){
		return this.containerName;
	}
	
	public String getBundleName(){
		return this.bundleName;
	}
	
	public String getProviderName(){
		return this.providerName;
	}
	public boolean isEndUserService() {
		
		return this.isEndUser;
	}

	
	/**
	 * 
	 * @param serviceName
	 * @return The UtilityFunction equation as defined at the annotations
	 * of the provider class
	 */
	/*public String getUtilityFunction (String serviceName){
		Service[] services;
		services = (Service[]) metadata.get("Service");
		
		for (Service sa: services){
			if (sa.serviceType().getCanonicalName().compareTo(serviceName)==0){
				return sa.utilityFunction();
			}
		}
		return null;
	}*/
	//------------------End of Public Getters---------------
	
    /**
     * Returns the <code>Service</code> annotation(if any), attached to the class in the parameter.
     * @param clazz The class to search for <code>Service</code> annotation
     * @return The Service annotation or null
     */
    private Service[] getServiceAnnotations(final Class<?> clazz)
    {
        final Annotation[] annotations = clazz.getDeclaredAnnotations();
        final ArrayList<Service> services = new ArrayList<Service>();
        
        for(Annotation an:annotations)
        {
        	if (an.annotationType().getSimpleName().equals("Service"))
        	{
        		if(((Service)an).isEndUserService())
        			this.isEndUser=true;
        		else
        			this.isEndUser=false;
        		
        		services.add((Service) an);
        	}
        }

        return services.toArray(new Service[services.size()]);
    }
    
   
    
    /**
     * Return all the annotations attached to fields of the Class in the parameter
     * @param clazz The class to search for Field annotations
     * @return An array that contains all the <code>RequireService</code> annotations found
     */
    private RequiredService[] getRequiredServiceAnnotation(final Class<?> clazz)
    {
    	
    	final Field[] fields;
    	java.util.Iterator<Annotation[]> itr;
    	
    	final ArrayList <Annotation[]> fieldAnnotations= new ArrayList<Annotation[]>();
        final ArrayList <RequiredService> requiredServiceList= new ArrayList<RequiredService>();
        fields = clazz.getDeclaredFields();
       // System.out.println("There are "+fields.length+" fields declared in "+clazz.getSimpleName());
       
        for (Field field:fields)
        {
        	if (field.getDeclaredAnnotations().length>0)
        	{
        		fieldAnnotations.add(field.getDeclaredAnnotations());
        	}
        }
        //System.out.println(fieldAnnotations.size()+" of them are annotated");
      
        itr = fieldAnnotations.iterator();
       
        while(itr.hasNext())
        {
        	Annotation[] an = itr.next();
        	for(Annotation a:an)
        	{
        		if (a.annotationType().getSimpleName().equals("RequiredService"))
        		{
        			
        			requiredServiceList.add((RequiredService) a);
        		}
        	}
        }
     
       return   (requiredServiceList.isEmpty()) ? null  : requiredServiceList.toArray(new RequiredService[requiredServiceList.size()]);
        
    }

	
	
}
